<!DOCTYPE html>
<html lang="zh-Hans">
<meta name="theme-color" content="#FFFFFF">


<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
  <meta name="theme-color" content="#202020"/>
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <link rel="shortcut icon" href="/images/favicon.ico" type="image/x-icon" />
  <link rel="icon" sizes="any" mask href="/images/favicon.ico" /> 
  
  

  <!-- viewjs support -->
  
    <!-- for theme: default is false -->
    <!-- for page: default is true -->
    <link  href="./css/viewer.min.css" rel="stylesheet" >
    <script src="./js/viewer.min.js" type="text/javascript" charset="utf-8"></script>
  
  
  
    <meta name="keywords" content="JVM,锁,偏向锁," />
  

  
    <meta name="description" content="分享 Java 技术，做有趣的程序员" />
  
  
  <link rel="icon" type="image/x-icon" href="/logo.png">
  <title>Java 中的偏向锁、轻量级锁和重量级锁 [ H E R O ]</title>
  
    <!-- stylesheets list from config.yml -->
    
      <link rel="stylesheet" href="//cdn.bootcss.com/pure/1.0.0/pure-min.css">
    
      <link rel="stylesheet" href="/css/xoxo.css">
    
  
<meta name="generator" content="Hexo 5.4.0"></head>


<body>
  <div class="nav-container">
    <nav id="menu" class="home-menu pure-menu pure-menu-horizontal">
  <a class="pure-menu-heading" href="/">
    <!-- <img class="avatar" src="/images/logo.png"> -->
    <span class="title">H E R O</span>
  </a>

  <ul class="pure-menu-list clearfix">
      
          
            <li class="pure-menu-item"><a href="/" class="pure-menu-link">首页</a></li>
          
      
          
            <li class="pure-menu-item"><a href="/archives/" class="pure-menu-link">归档</a></li>
          
      
          
            <li class="pure-menu-item"><a href="/tags/" class="pure-menu-link">标签</a></li>
          
      
          
            <li class="pure-menu-item"><a href="/about/" class="pure-menu-link">关于</a></li>
          
      
          
            <li class="pure-menu-item"><a href="/atom.xml" class="pure-menu-link">订阅</a></li>
          
      
  </ul>
   
</nav>
  </div>

  <div class="container" id="content-outer">
    <div class="inner" id="content-inner">
      <div class="post-container">
  <article class="post" id="post">
    <header class="post-header text-center">
      <h1 class="title">
        Java 中的偏向锁、轻量级锁和重量级锁
      </h1>
      <span>
        
        <time class="time" datetime="2019-07-18T16:28:02.000Z">
          2019-07-19
        </time>
        
      </span>
      <span class="slash">/</span>
      <span class="post-meta">
        <span class="post-tags">
          <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JVM/" rel="tag">JVM</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E5%81%8F%E5%90%91%E9%94%81/" rel="tag">偏向锁</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E9%94%81/" rel="tag">锁</a></li></ul>
        </span>
      </span>
      
     
    </header>

    <div class="post-content">
      <p>今天我们来聊聊 Synchronized 里面的各种锁：偏向锁、轻量级锁、重量级锁，以及三个锁之间是如何进行锁膨胀的。</p>
<span id="more"></span>
<p><img src="../media/GodsGarden_ZH-CN3317703606_1920x1080.jpg" alt="GodsGarden_ZH-CN3317703606_1920x1080"></p>
<p>众所周知，线程阻塞带来的上下文切换的代价是很大的，Java 为了尽量减少上下文的切换从而引入了更多的锁机制。在了解各种锁机制之前，先要学习一些前置知识。对于各种锁的获取和释放、以及锁升级的流程，在文末总结处有一张图，如果赶时间，直接看图吧。</p>
<h2 id="Mark-Word"><a href="#Mark-Word" class="headerlink" title="Mark Word"></a>Mark Word</h2><p><img src="../media/image-20190719003918974.png" alt="image-20190719003918974"></p>
<p>Java 对象头里面有一部分叫做 Mark Word，在32位虚拟机下面占有 32bit，在 64 位虚拟机下面占用 64 bit，本文以 32 位虚拟机为例子。</p>
<h2 id="锁介绍"><a href="#锁介绍" class="headerlink" title="锁介绍"></a>锁介绍</h2><p><strong>偏向锁：</strong>一个线程反复的去获取/释放一个锁，如果这个锁是轻量级锁或者重量级锁，不断的加解锁显然是没有必要的，造成了资源的浪费。于是引入了偏向锁，偏向锁在获取资源的时候会在资源对象上记录该对象是偏向该线程的，偏向锁并不会主动释放，这样每次偏向锁进入的时候都会判断改资源是否是偏向自己的，如果是偏向自己的则不需要进行额外的操作，直接可以进入同步操作。</p>
<p><strong>轻量级锁：</strong>轻量级锁是由偏向所升级来的，偏向锁运行在一个线程进入同步块的情况下，当第二个线程加入锁争用的时候，偏向锁就会升级为轻量级锁； 在轻量级锁中如果发生多线程竞争，未持有锁的线程会自旋等待。</p>
<p><strong>重量级锁：</strong>由操作系统的 mutex 来实现，多线程竞争下，未持有锁的线程将被阻塞。</p>
<h2 id="锁的流转"><a href="#锁的流转" class="headerlink" title="锁的流转"></a>锁的流转</h2><p><img src="../media/lock-of-jvm.jpeg" alt="锁转移"></p>
<h3 id="对象分配"><a href="#对象分配" class="headerlink" title="对象分配"></a>对象分配</h3><p>首先确定该类的偏向锁是否可用（决定了在新建对象实例的时候倒数第三 bit 是 0 还是 1）；如果不可用，直接使用轻量锁。如果偏向锁可用，新建实例对象 obj，此时 obj 进入到未锁定、未偏向、可偏向的状态。</p>
<h3 id="偏向锁-初始锁定"><a href="#偏向锁-初始锁定" class="headerlink" title="偏向锁 初始锁定"></a>偏向锁 初始锁定</h3><p>此时线程 A 想要获取对象 obj 的偏向锁，由于此时 obj 没有偏向任何线程（有可能是刚刚新建，也有可能是由锁定状态重偏向之后导致的未锁定状态），所以利用 CAS 操作将线程 ID 写入到 Mark Word 里面，此时 线程 A 获取了 obj 的偏向锁。obj 处于已偏向、锁定状态。</p>
<h3 id="偏向锁-锁定-解锁"><a href="#偏向锁-锁定-解锁" class="headerlink" title="偏向锁 锁定/解锁"></a>偏向锁 锁定/解锁</h3><p>一旦 obj 第一次偏向了线程 A，A 就可以在没有竞争的情况下，也就是锁不升级的情况下，以极小的代价反复获取 obj 对象的锁。</p>
<h3 id="偏向锁-重偏向（rebias）"><a href="#偏向锁-重偏向（rebias）" class="headerlink" title="偏向锁 重偏向（rebias）"></a>偏向锁 重偏向（rebias）</h3><p>如果 obj 先被线程 A 锁定，然后释放。然后线程 B 过来是否能重新获取偏向锁呐？在一定条件下，经过了重偏向可以重新获得偏向锁。</p>
<p>通过上面的 mark word 我们可以看出，在偏向锁的时候有一个字段是 epoch，同时在 obj 的类 O 信息里面也有一个 epoch，每次系统到达安全点会对类的 epoch 加 1，变成 epoch_new，然后扫描所有的类 O 的实例，判断该偏向锁是否还被持有，如果被持有则将 epoch_new 复制给对象头的 epoch 字段。</p>
<p>每次去获取偏向锁的时候回去判断对象实例的 epoch 和 类的 epoch 是否相等，如果不等代表对象是未锁定、可偏向、未偏向状态，可以偏向新的线程。</p>
<h3 id="撤销偏向（revoke）"><a href="#撤销偏向（revoke）" class="headerlink" title="撤销偏向（revoke）"></a>撤销偏向（revoke）</h3><blockquote>
<p> Once biased, that thread can subsequently lock and unlock the object without resorting to expensive atomic instructions. Obviously, an object can be biased toward at most one thread at any given time. (We refer to that thread as the bias holding thread). If another thread tries to acquire a biased object, however, we need to <em>revoke</em> the bias from the original thread. </p>
</blockquote>
<p>线程 B 来竞争，此时偏向锁会膨胀位轻量级锁。当 B 线程想利用 CAS 获取偏向锁失败， A 线程被暂停，然后检查 A 线程；</p>
<ol>
<li><p>A 线程已经退出了同步代码块，或者是已经不在存活了，如果是上面两种情况之一的，将 obj 的 Mark Word 后 3bit 改为 001（无锁状态），然后再去唤醒 A 线程。</p>
</li>
<li><p>A 线程还在同步代码块中，此时将 A 线程的偏向锁升级为轻量级锁。首先会 copy 一份 obj 对象的 mark word 内容，放到线程 A 当前栈帧的一个叫做 Lock Record 空间的 displace mark word 的位置，</p>
<p><img src="../media/20170420102716139.jpeg" alt="20170420102716139"></p>
<p>然后通过 CAS 将 obj 中 mark word 的内容替换为指向栈帧中 mark word 的位置，最后将 Lock Record 里面的 owner 指向 obj 对象。并且对象Mark Word的锁标志位设置为“00”，到此完成了锁的升级。</p>
<p><img src="../media/20170420102754608.jpeg" alt="20170420102754608"></p>
</li>
</ol>
<h3 id="Bulk-Revoke"><a href="#Bulk-Revoke" class="headerlink" title="Bulk Revoke"></a>Bulk Revoke</h3><p>Java 会在类信息里面维护一个计数器记录，记录该类发生偏向锁 revoke 的次数，一旦达到某个阈值，则认为这个类不适合偏向锁，将会禁用这个类的偏向锁功能。这个类新建的对象实例的最后 3bit 将是 001（无锁）。</p>
<blockquote>
<p>Objects that are explicitly designed to be shared between multiple threads, such as producer/consumer queues, are not suitable for biased locking. Therefore, biased locking is disabled for a class if revocations for its instances happened frequently in the past. This is called <em>bulk revocation</em>. If the locking code is invoked on an instance of a class for which biased locking was disabled, it performs the standard thin locking. Newly allocated instances of the class are marked as non-biasable.</p>
</blockquote>
<h3 id="轻量级锁定"><a href="#轻量级锁定" class="headerlink" title="轻量级锁定"></a>轻量级锁定</h3><p>刚刚在上文中关于撤销偏向（revoke）中已经描述了锁定的流程。在此再总结一遍</p>
<ol>
<li>复制 mark word 到 displace mark word</li>
<li>CAS 替换 mark word 中的信息为指针</li>
<li>修改 锁 标志为 00</li>
<li>将栈帧中的 owner 指向锁定的对象</li>
</ol>
<h3 id="轻量级锁-递归锁定"><a href="#轻量级锁-递归锁定" class="headerlink" title="轻量级锁 递归锁定"></a>轻量级锁 递归锁定</h3><p><img src="../media/image-20190719112951132.png" alt="image-20190719112951132"></p>
<p>线程 A 在去获取轻量级锁的时候，会首先使用 CAS 操作，如果操作失败那么会在此时判断是不是该线程已经持有过该对象的锁了，通过判断对象的 mark word 是不是指向当前线程的栈帧，如果是则会在最新的栈帧处新建一个 displaced mark word 为 null 的 lock record。关于为什么要这么做，其实就是为了记录一下锁的重入，发生重入了需要记录，本来是记录到 mark word 里面最方便，可能是 mark word 没有足够的空间。如果是在 lock record里面记录的话，需要遍历 lock record 才可以获取这个数量。所以最终Hotspot选择每次获得锁都添加一个<code>Lock Record</code>来表示锁的重入。</p>
<p>类似的递归的解锁只需要将栈帧中的 lock records 删除即可。</p>
<h3 id="轻量级锁-膨胀"><a href="#轻量级锁-膨胀" class="headerlink" title="轻量级锁 膨胀"></a>轻量级锁 膨胀</h3><p>线程 A 持有对象 obj 的轻量级锁，线程 B 过来 CAS 失败，开始自旋等待，自旋到达一定次数之后如果还没有获取该轻量级锁，则会将该锁膨胀为重量级锁。会将 mark word 的的锁标志为改为10，同时经指针指向互斥量，然后线程 B 挂起。</p>
<blockquote>
<p>JDK1.6中 -XX:+UseSpinning开启； -XX:PreBlockSpin=10 为自旋次数。<br>JDK1.7后，去掉 -XX:PreBlockSpin 参数，由jvm控制。</p>
</blockquote>
<h3 id="轻量级锁-解锁"><a href="#轻量级锁-解锁" class="headerlink" title="轻量级锁 解锁"></a>轻量级锁 解锁</h3><p>轻量级锁解锁也是理由 CAS 将 mark word 里面的指针替换为无锁的 mark word 信息。需要判断 mark word 里面是指向该线程的 lock record</p>
<ol>
<li>如果不是，说明已经锁膨胀了，CAS 失败，此时需要唤醒在等待重量级锁的线程</li>
<li>如果是，说明锁没有膨胀，直接 CAS 操作将 mark word 改为 001（无锁）状态，为下一个线程获取轻量级锁做好准备。</li>
</ol>
<h3 id="重量级锁-解锁"><a href="#重量级锁-解锁" class="headerlink" title="重量级锁 解锁"></a>重量级锁 解锁</h3><p>释放 mutex，唤醒在等待的线程</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>新标签页打开图片可看大图</p>
<p><img src="../media/v2-9db4211af1be81785f6cc51a58ae6054_r.jpg" alt="v2-9db4211af1be81785f6cc51a58ae6054_r"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a target="_blank" rel="noopener" href="https://blog.csdn.net/zqz_zqz/article/details/70233767">java 中的锁 — 偏向锁、轻量级锁、自旋锁、重量级锁 - zqz_zqz的博客 - CSDN博客</a></p>
<p><a target="_blank" rel="noopener" href="https://zhuanlan.zhihu.com/p/34606653">Java 8 并发篇 - 冷静分析 Synchronized（下） - 知乎</a></p>
<p><a target="_blank" rel="noopener" href="https://blogs.oracle.com/dave/biased-locking-in-hotspot">Biased Locking in HotSpot | Oracle David Dice’s Blog</a>  //rebias</p>
<p><a target="_blank" rel="noopener" href="http://www.ishenping.com/ArtInfo/266444.html#Bulk_Rebias_192">Java中的偏向锁，轻量级锁， 重量级锁解析 - 神评网</a> //rebias</p>
<p><a target="_blank" rel="noopener" href="https://www.zhihu.com/question/56582060"> BiasedLocking模式下markOop中位域epoch的根本作用是什么？ - 知乎</a> //rebias</p>
<p><a target="_blank" rel="noopener" href="https://juejin.im/post/5bfffafc6fb9a049d37ed198">死磕Synchronized底层实现—轻量级锁 - 掘金</a></p>

    </div>
    <br>
    <div>全文完。</div>
  </article>
  <div class="toc-container">
    
  <div id="toc" class="toc-article">
    <strong class="toc-title">目录</strong>
    <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Mark-Word"><span class="toc-text">Mark Word</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%94%81%E4%BB%8B%E7%BB%8D"><span class="toc-text">锁介绍</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%94%81%E7%9A%84%E6%B5%81%E8%BD%AC"><span class="toc-text">锁的流转</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D"><span class="toc-text">对象分配</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%81%8F%E5%90%91%E9%94%81-%E5%88%9D%E5%A7%8B%E9%94%81%E5%AE%9A"><span class="toc-text">偏向锁 初始锁定</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%81%8F%E5%90%91%E9%94%81-%E9%94%81%E5%AE%9A-%E8%A7%A3%E9%94%81"><span class="toc-text">偏向锁 锁定&#x2F;解锁</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%81%8F%E5%90%91%E9%94%81-%E9%87%8D%E5%81%8F%E5%90%91%EF%BC%88rebias%EF%BC%89"><span class="toc-text">偏向锁 重偏向（rebias）</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%92%A4%E9%94%80%E5%81%8F%E5%90%91%EF%BC%88revoke%EF%BC%89"><span class="toc-text">撤销偏向（revoke）</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Bulk-Revoke"><span class="toc-text">Bulk Revoke</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81%E5%AE%9A"><span class="toc-text">轻量级锁定</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81-%E9%80%92%E5%BD%92%E9%94%81%E5%AE%9A"><span class="toc-text">轻量级锁 递归锁定</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81-%E8%86%A8%E8%83%80"><span class="toc-text">轻量级锁 膨胀</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81-%E8%A7%A3%E9%94%81"><span class="toc-text">轻量级锁 解锁</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81-%E8%A7%A3%E9%94%81"><span class="toc-text">重量级锁 解锁</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-text">总结</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E8%80%83"><span class="toc-text">参考</span></a></li></ol>
  </div>


  </div>
</div>
<div class="copyright">
    <span>本作品采用</span>
    <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by/4.0/">知识共享署名 4.0 国际许可协议</a>
    <span>进行许可。 转载时请注明原文链接。</span>
</div>
<div class="share" style="width: 100%;">
  <img src="/images/wechat-qcode.jpg" height="200" width="200" alt="" style="margin: auto; display: block;" />

  <div style="margin: auto; text-align: center; font-size: 0.8em; color: grey;">关注“豆菽技术”公众号</div>
  
</div>

  
    <div class="post-nav">
      <div class="post-nav-item post-nav-next">
        
          <span>〈 </span>
          <a href="/2PC-and-3PC.html" rel="next" title="分布式一致性协议学习笔记：2PC 和 3PC">
          分布式一致性协议学习笔记：2PC 和 3PC
          </a>
        
      </div>
  
      <div class="post-nav-item post-nav-prev">
          
          <a href="/solve-dp-problems.html" rel="prev" title="如何解决动态规划问题">
            如何解决动态规划问题
          </a>
          <span>〉</span>
        
      </div>
    </div>
  

  <section class="disqus-comments">
    <div id="disqus_thread">
      <noscript>Please enable JavaScript to view the <a target="_blank" rel="noopener" href="//disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
    </div>
  </section>

  <script>
    var disqus_shortname = 'coderbean';
    
    var disqus_url = 'https://jacobchang.cn/lock-of-synchronized.html';
    
    (function(){
      var dsq = document.createElement('script');
      dsq.type = 'text/javascript';
      dsq.async = true;
      dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
      (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
  </script>

  <script id="dsq-count-scr" src="//coderbean.disqus.com/count.js" async></script>



    </div>

    

  </div>
  <footer class="footer text-center">
    <div id="bottom-inner">
        <a class="bottom-item-hide" href="https://jacobchang.cn/" target="_blank">主站</a>
        <!-- <a class="bottom-item-hide">·</a> -->
        <!-- <a class="bottom-item-hide" href="" target="_blank">备份站点</a> -->
        <a class="bottom-item-hide">·</a>
        <a class="bottom-item-hide" href="https://github.com/coderbean" target="_blank">GitHub</a>
        <a class="bottom-item-hide">·</a>
        <a class="bottom-item" href="https://hexo.io" target="_blank">Powered by hexo</a>
        <a class="bottom-item">·</a>
        <a class="bottom-item" href="https://github.com/coderbean/hexo-theme-xoxo" target="_blank">Theme xoxo</a>
    </div>
    <div id="bottom-inner">
        <a class="bottom-item-hide" target="_blank" rel="noopener" href="https://beian.miit.gov.cn">备案号：浙ICP备2021033778号-1</a> 
        <a class="bottom-item-hide">·</a>
        <a id="copyright" class="bottom-item"></a>
    </div>
</footer>

<script language="javascript" type="text/javascript">
    var df = new Date();
    var year = df.getFullYear();
    document.getElementById("copyright").innerHTML = "Copyright © 2017 – " + year + " 黄小豆";
</script>
  

<script>
  (function(window, document, undefined) {

    var timer = null;

    function returnTop() {
      cancelAnimationFrame(timer);
      timer = requestAnimationFrame(function fn() {
        var oTop = document.body.scrollTop || document.documentElement.scrollTop;
        if (oTop > 0) {
          document.body.scrollTop = document.documentElement.scrollTop = oTop - 50;
          timer = requestAnimationFrame(fn);
        } else {
          cancelAnimationFrame(timer);
        }
      });
    }

    var hearts = [];
    window.requestAnimationFrame = (function() {
      return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function(callback) {
          setTimeout(callback, 1000 / 60);
        }
    })();
    init();

    function init() {
      css(".heart{z-index:9999;width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: absolute;}.heart:after{top: -5px;}.heart:before{left: -5px;}");
      attachEvent();
      gameloop();
      addMenuEvent();
    }

    function gameloop() {
      for (var i = 0; i < hearts.length; i++) {
        if (hearts[i].alpha <= 0) {
          document.body.removeChild(hearts[i].el);
          hearts.splice(i, 1);
          continue;
        }
        hearts[i].y--;
        hearts[i].scale += 0.004;
        hearts[i].alpha -= 0.013;
        hearts[i].el.style.cssText = "left:" + hearts[i].x + "px;top:" + hearts[i].y + "px;opacity:" + hearts[i].alpha + ";transform:scale(" + hearts[i].scale + "," + hearts[i].scale + ") rotate(45deg);background:" + hearts[i].color;
      }
      requestAnimationFrame(gameloop);
    }

    /**
     * 设置点击事件
     * 
     * - 回到顶部
     * - 出现爱心
     */
    function attachEvent() {
      var old = typeof window.onclick === "function" && window.onclick;
      var logo = document.getElementById("menu");
      if (logo) {
        logo.onclick = function(event) {
          // returnTop();
          old && old();
          createHeart(event);
        }
      }
      
    }

    function createHeart(event) {
      var d = document.createElement("div");
      d.className = "heart";
      hearts.push({
        el: d,
        x: event.clientX - 5,
        y: event.clientY - 5,
        scale: 1,
        alpha: 1,
        color: randomColor()
      });
      document.body.appendChild(d);
    }

    function css(css) {
      var style = document.createElement("style");
      style.type = "text/css";
      try {
        style.appendChild(document.createTextNode(css));
      } catch (ex) {
        style.styleSheet.cssText = css;
      }
      document.getElementsByTagName('head')[0].appendChild(style);
    }

    function randomColor() {
      // return "rgb(" + (~~(Math.random() * 255)) + "," + (~~(Math.random() * 255)) + "," + (~~(Math.random() * 255)) + ")";
      return "#F44336";
    }

    function addMenuEvent() {
      var menu = document.getElementById('menu-main-post');
      if (menu) {
        var toc = document.getElementById('toc');
        if (toc) {
          menu.onclick = function() {
            if (toc) {
              if (toc.style.display == 'block') {
                toc.style.display = 'none';
              } else {
                toc.style.display = 'block';
              }
            }
          };
        } else {
          menu.style.display = 'none';
        }
      }
    }

  })(window, document);
</script>

  


  <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
                  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
              m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
      ga('create', 'UA-155080039-1', 'auto');
      ga('send', 'pageview');
  </script>


  
<link rel='stylesheet' href='https://s3-us-west-2.amazonaws.com/s.cdpn.io/1462889/unicons.css'>

<div class="progress-wrap">
    <svg class="progress-circle svg-content" width="100%" height="100%" viewBox="-1 -1 102 102">
        <path d="M50,1 a49,49 0 0,1 0,98 a49,49 0 0,1 0,-98" />
    </svg>
</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js'></script>
<script>

    (function ($) {
        "use strict";
        $(document).ready(function () {
            "use strict";

            //Scroll back to top

            var progressPath = document.querySelector('.progress-wrap path');
            var pathLength = progressPath.getTotalLength();
            progressPath.style.transition = progressPath.style.WebkitTransition = 'none';
            progressPath.style.strokeDasharray = pathLength + ' ' + pathLength;
            progressPath.style.strokeDashoffset = pathLength;
            progressPath.getBoundingClientRect();
            progressPath.style.transition = progressPath.style.WebkitTransition = 'stroke-dashoffset 10ms linear';
            var updateProgress = function () {
                var scroll = $(window).scrollTop();
                var height = $(document).height() - $(window).height();
                var progress = pathLength - (scroll * pathLength / height);
                progressPath.style.strokeDashoffset = progress;
            }
            updateProgress();
            $(window).scroll(updateProgress);
            var offset = 50;
            var duration = 550;
            jQuery(window).on('scroll', function () {
                if (jQuery(this).scrollTop() > offset) {
                    jQuery('.progress-wrap').addClass('active-progress');
                } else {
                    jQuery('.progress-wrap').removeClass('active-progress');
                }
            });
            jQuery('.progress-wrap').on('click', function (event) {
                event.preventDefault();
                jQuery('html, body').animate({ scrollTop: 0 }, duration);
                return false;
            })


        });

    })(jQuery);
</script>

  
<!-- for theme: default is false -->
<!-- for page: default is true -->
<!-- 图片查看器实例配置 -->
<script type="text/javascript">
    //默认设置， 可以根据个人需求和喜好进行配置
    //详细参考官方说明
    Viewer.setDefaults({
        //设置初始缩放 default: 1
        zoomRatio: [0.5],
        // 过渡动画
        transition:true,
        // 显示工具条
        toolbar: false,
    });
    //获得content中所有的图片， 不同主题可能有所不同
    //为了和其他的图片区别开来 所以在markdown中插入图片的时候使用独特的记号
    //为了一次性得到所有的图片我这里采用的是class = 'article-image'
    var article = document.querySelector('.post-content');
    if(article!=null) {
        var imageList = article.getElementsByTagName('img');
        //将获取到的HTMLCollections转化成Array
        var imageArray = new Array();
        Array.prototype.forEach.call(imageList, element => {
            imageArray.push(element);
        });
        //设置每个图片成为图片组
        Array.prototype.forEach.call(imageList, element => {
            var viewer1 = new Viewer(element);
            viewer1.images = imageArray;
            viewer1.length = imageArray.length;
        });
    }

    
</script>

  
  <! -- mathjax config similar to math.stackexchange -->

<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
                processEscapes: true
                    
}
  
        });
</script>

<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
            skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
                  
}
    
        });
</script>

<script type="text/x-mathjax-config">
MathJax.Hub.Queue(function() {
            var all = MathJax.Hub.getAllJax(), i;
            for(i=0; i < all.length; i += 1) {
                            all[i].SourceElement().parentNode.className += ' has-jax';
                                    
            }
                
        });
</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML"></script>

</script>

  
</body>

</html>